﻿using System.Security.Cryptography;
using System.Text;
using System.Xml;

namespace Sage.WXWork
{
    /// <summary>
    /// 微信官方签名
    /// </summary>
    /// <param name="sToken"></param>
    /// <param name="sEncodingAESKey"></param>
    /// <param name="sReceiveId"></param>
    public class WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sReceiveId)
    {
        private readonly string m_sToken = sToken;
        private readonly string m_sEncodingAESKey = sEncodingAESKey;
        private readonly string m_sReceiveId = sReceiveId;

        enum WXBizMsgCryptErrorCode
        {
            WXBizMsgCrypt_OK = 0,
            /// <summary>
            /// 签名验证错误
            /// </summary>
            WXBizMsgCrypt_ValidateSignature_Error = -40001,
            /// <summary>
            /// xml/json解析失败
            /// </summary>
            WXBizMsgCrypt_ParseXml_Error = -40002,
            /// <summary>
            /// sha加密生成签名失败
            /// </summary>
            WXBizMsgCrypt_ComputeSignature_Error = -40003,
            /// <summary>
            /// AESKey 非法
            /// </summary>
            WXBizMsgCrypt_IllegalAesKey = -40004,
            /// <summary>
            /// ReceiveId 校验错误
            /// </summary>
            WXBizMsgCrypt_ValidateCorpid_Error = -40005,
            /// <summary>
            /// AES 加密失败
            /// </summary>
            WXBizMsgCrypt_EncryptAES_Error = -40006,
            /// <summary>
            /// AES 解密失败
            /// </summary>
            WXBizMsgCrypt_DecryptAES_Error = -40007,
            /// <summary>
            /// 解密后得到的buffer非法
            /// </summary>
            WXBizMsgCrypt_IllegalBuffer = -40008,
            /// <summary>
            /// base64加密失败
            /// </summary>
            WXBizMsgCrypt_EncodeBase64_Error = -40009,
            /// <summary>
            /// base64解密失败
            /// </summary>
            WXBizMsgCrypt_DecodeBase64_Error = -40010,
            /// <summary>
            /// 生成xml/json失败
            /// </summary>
            WXBizMsgCrypt_GenerateXml_Error = -40011,

        };

        /// <summary>
        /// 验证URL
        /// </summary>
        /// <param name="sMsgSignature"></param>
        /// <param name="sTimeStamp"></param>
        /// <param name="sNonce"></param>
        /// <param name="sEchoStr"></param>
        /// <param name="sReplyEchoStr"></param>
        /// <returns></returns>
        public int VerifyURL(string sMsgSignature, string sTimeStamp, string sNonce, string sEchoStr, ref string sReplyEchoStr)
        {
            if (m_sEncodingAESKey.Length != 43)
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;

            int ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEchoStr, sMsgSignature);
            if (ret != 0)
                return ret;

            string cpid = string.Empty;
            try
            {
                sReplyEchoStr = Cryptography.AES_decrypt(sEchoStr, m_sEncodingAESKey, ref cpid);
            }
            catch (Exception)
            {
                sReplyEchoStr = string.Empty;
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
            }

            if (cpid != m_sReceiveId)
            {
                sReplyEchoStr = string.Empty;
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
            }
            return 0;
        }

        /// <summary>
        /// 消息处理
        /// </summary>
        /// <param name="sMsgSignature"></param>
        /// <param name="sTimeStamp"></param>
        /// <param name="sNonce"></param>
        /// <param name="sPostData"></param>
        /// <param name="sMsg"></param>
        /// <returns></returns>
        public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
        {
            if (m_sEncodingAESKey.Length != 43)
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;

            XmlDocument doc = new();
            string sEncryptMsg;
            try
            {
                doc.LoadXml(sPostData);
                XmlNode? root = doc.FirstChild;
                if (root == null)
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;

                XmlNode? encryptNode = root["Encrypt"];
                if (encryptNode == null)
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;

                sEncryptMsg = encryptNode.InnerText;
            }
            catch (Exception)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
            }

            // 验证签名
            int ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
            if (ret != 0)
                return ret;

            // 解密
            string cpid = string.Empty;
            try
            {
                sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
            }
            catch (FormatException)
            {
                sMsg = string.Empty;
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
            }
            catch (Exception)
            {
                sMsg = string.Empty;
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
            }

            if (cpid != m_sReceiveId)
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;

            return 0;
        }

        /// <summary>
        /// 消息处理
        /// </summary>
        /// <param name="sReplyMsg"></param>
        /// <param name="sTimeStamp"></param>
        /// <param name="sNonce"></param>
        /// <param name="sEncryptMsg"></param>
        /// <returns></returns>
        public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
        {
            if (m_sEncodingAESKey.Length != 43)
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;

            string raw;
            try
            {
                raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sReceiveId);
            }
            catch (Exception)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
            }

            string MsgSigature = string.Empty;
            int ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
            if (ret != 0)
                return ret;

            sEncryptMsg = "<xml>" +
                          $"<Encrypt><![CDATA[{raw}]]></Encrypt>" +
                          $"<MsgSignature><![CDATA[{MsgSigature}]]></MsgSignature>" +
                          $"<TimeStamp><![CDATA[{sTimeStamp}]]></TimeStamp>" +
                          $"<Nonce><![CDATA[{sNonce}]]></Nonce>" +
                          "</xml>";

            return 0;
        }

        /// <summary>
        /// 排序
        /// </summary>
        public class DictionarySort : IComparer<string>
        {
            /// <summary>
            /// 
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <returns></returns>
            public int Compare(string? x, string? y)
            {
                string sLeft = x ?? string.Empty;
                string sRight = y ?? string.Empty;

                int iLeftLength = sLeft.Length;
                int iRightLength = sRight.Length;
                int index = 0;

                while (index < iLeftLength && index < iRightLength)
                {
                    if (sLeft[index] < sRight[index])
                        return -1;
                    else if (sLeft[index] > sRight[index])
                        return 1;
                    else
                        index++;
                }

                return iLeftLength - iRightLength;
            }
        }

        private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
        {
            string hash = string.Empty;
            int ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
            if (ret != 0)
                return ret;

            return hash == sSigture ? 0 : (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
        }

        /// <summary>
        /// 生成签名
        /// </summary>
        /// <param name="sToken"></param>
        /// <param name="sTimeStamp"></param>
        /// <param name="sNonce"></param>
        /// <param name="sMsgEncrypt"></param>
        /// <param name="sMsgSignature"></param>
        /// <returns></returns>
        public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature)
        {
            var AL = new List<string> { sToken, sTimeStamp, sNonce, sMsgEncrypt };
            AL.Sort(new DictionarySort());

            string raw = string.Join("", AL);

            try
            {
                byte[] dataToHash = Encoding.ASCII.GetBytes(raw);
                byte[] dataHashed = SHA1.HashData(dataToHash);

                sMsgSignature = Convert.ToHexString(dataHashed).ToLower();
            }
            catch (Exception)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
            }

            return 0;
        }
    }
}
